1.ExoPlayer简介

ExoPlayer是google开源的应用级媒体播放器项目。

与内置的MediaPlayer相比,ExoPlayer的优点主要有:

  • 支持通过Http(DASH)和SmoothStreaming进行动态自适应流,这两种都不受MediaPlayer的支持。它还支持其他格式的数据资源,比如MP4、M4A、FMP4、MKV、MP3、Ogg、WAV、FLV等
  • 支持高级的HLS个性,比如正确处理#EXT-X-DISCONTINUITY标签
  • 无缝连接,合并和循环播放多媒体的能力
  • 和应用一起更新播放器(ExoPlayer),因为ExoPlayer是一个集成到应用APK的库,可以随着应用的更新把ExoPlayer更新到一个更新的版本。
  • 在不同的Android版本和设备上很少会有不同的表现。
  • 能够自定义和扩展播放器,以适应各种不同的需求
  • 各个组件都可以自定义,还可以接入ffmpeg组件

缺点就是,在音频播放时ExoPlayer会比MediaPlayer消耗更多的电量。

集成

在app的module中的build.gradle文件添加如下依赖(这种方式是添加全部的依赖):
implementation 'com.google.android.exoplayer:exoplayer:2.X.X'

如果不需要依赖全部的类库,也可以只选择添加你需要的类库,例如下面的方式是只需要播放DASH内容的app,就是只添加Core、DASH和UI库:

implementation 'com.google.android.exoplayer:exoplayer-core:2.X.X'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.X.X'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.X.X'

下面列出了所有的依赖库:

  • exoplayer-core:核心功能库(基础库、必要)
  • exoplayer-dash:支持DASH
  • exoplayer-hls:支持HLS
  • exoplayer-smoothstreaming:支持SmoothStreaming
  • exoplayer-ui:使用ExoPlayer所需的UI部分和资源

上面讲到了DASH、HLS、SmoothStreaming,具体的介绍可以参考流媒体通信协议

除了上面的几个类库外,ExoPlayer还有很多扩展库提供一些额外的功能,有一些可以直接通过JCenter进行依赖,有一些需要手动去编译,具体的可以通过扩展库目录中的README来查看详细的内容。 可以通过JCenter进行依赖的类库和扩展可以从ExoPlayer的Binatry上查看。

如果依赖完后仍然不行,你需要将build.gradle文件中的android节点打开Java 8的支持:

compileOptions {
    targetCompatibility = 1.8
}

ExoPlayer库的核心是ExoPlayer接口,ExoPlayer公开了传统的高级媒体播放器功能,例如缓冲、播放、暂停、seek等。在具体实现方面,该开源库对播放器的媒体类型、存储方式、位置、渲染方式等进行了最少的实现,旨在让开发者自定义各种特性。ExoPlayer的实现不是直接实现加载和呈现媒体,而是将这项工作委托给各种组件。主要有:

  • TrackSelector:轨道提取器,从MediaSource中提取各个轨道的二进制数据,交给Renderer渲染,创建播放器时传入。
  • Renderer:对多媒体中的各个轨道(音轨、视频轨、字母轨等)数据进行渲染,渲染就是"播放",把二进制文件渲染成声音、画面,创建播放器时传入。
  • MediaSource:定义多媒体数据源,这个类的功能就是从Uri中读取多媒体文件的二进制数据。MediaSource在播放开始时通过ExoPlayer.prepare()注入。
  • LoadControl:对MediaSource进行控制,比如什么时候开始缓冲、缓冲多少等。

它们之间的关系是:

渲染器(Render) ---刷数据--->提取器(Extraor) ----读取数据---> 加载控制器(LoadControl) ----控制数据加载方式---> 媒体源(MediaSource)

该库提供了这些组件的默认实现,既能满足大部分需求,也可通过自定义来实现特殊的需求。例如可以通过自定义LoadControl来更改播放器的缓冲策略,或自定义Renderer来渲染Android本身不支持的编解码器。

支持的格式

创建播放器

为了满足不同的需求,ExoPlayer提供了一个工厂类ExoPlayerFactory,通过该工厂类来创建一个ExoPlayer实例,大多数情况下直接使用ExoPlayerFactory.newSimpleInstance(context)方法即可:

public static SimpleExoPlayer newSimpleInstance(Context context) {
    return newSimpleInstance(context, new DefaultTrackSelector());
}

它返回的SimpleExoPlayer是一个实现ExoPlayer接口并添加了一些额外的高级播放器功能。

把播放器实例附着到一个View上

ExoPlayer库提供了一个PlayerView(A high level view for Player media playbacks. It displays video, subtitles and album art during playback, and displays playback controls using a PlayerControlView.)类,他封装了PlayerControlView和渲染视频的一个默认的SurfaceView以及字幕等功能。可以通过xml中surface_type来指定视频播放的Surface类型,除了值spherical_view(这是球形视频播放一个特殊的值)时,允许值是surface_view,texture_view和none。如果视图仅用于音频播放,则应使用none以避免必须创建Surface,因为这样做可能耗费资源。

如果视图是用于常规视频播放那么surface_view或texture_view 应该使用。对于视频播放,相比TextureView,SurfaceView有许多好处:

  • 显着降低了许多设备的功耗。
  • 更准确的帧定时,使视频播放更流畅。
  • 播放受DRM保护的内容时支持安全输出。 因此,相比较于TextureView,SurfaceView应尽可能优先考虑。 TextureView只有在SurfaceView不符合您需求的情况下才能使用。一个示例是在Android N之前需要平滑动画或滚动视频表面,如下所述。对于这种情况,最好 TextureView 只在SDK_INT小于24(Android N)时使用, 否则,使用SurfaceView。

SurfaceView在Android N之前,渲染未与视图动画正确同步。在早期版本SurfaceView中,当放入滚动容器或受到动画影响时,这可能会导致不必要的效果 。这些效果包括视图的内容看起来略微落后于它应该显示的位置,并且视图在受到动画时变黑。为了在Android N之前实现流畅的动画或视频滚动,因此必须使用TextureView而不是SurfaceView。

xml声明:

<com.google.android.exoplayer2.ui.PlayerView
    android:id="@+id/mPlayerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
private lateinit var mPlayer: SimpleExoPlayer
private fun initView(context: Context) {
    val view = View.inflate(context, R.layout.video_view, this)
    mPlayer = ExoPlayerFactory.newSimpleInstance(context)
    // 通过PlayerView.setPlayer方法来将ExoPlayer绑定到View上
    mPlayerView.player = mPlayer
}

开始播放

ExoPlayer中将每一种媒体资源都封装成MediaSource,如果想要播放一种媒体资源,首先需要为他创建对应的MediaSource对象,然后把这个对象传递给ExoPlayer.prepared方法。ExoPlayer提供了多种MediaSource的实现类,例如播放DASH的DashMediaSource,SmoothStreaming的SsMediaSource,HLS的HlsMediaSource,以及顺序流媒体文件的ProgressiveMediaSource(插一嘴,流式传输分两种方法:实时流式传输方式(Realtime Streaming)和顺序流式传输方式(Progressive Streaming)。)。

    fun playSingleMp4Video(url: String) {
        val uri = Uri.parse(url)
        val mediaSource = buildMediaSource(uri)
        // setPlayWhenReady()方法可以设置prepared完成后是否自动播放,如果已经准备好了该方法可实现开始和暂停播放的功能
        mPlayer.playWhenReady = true
        // setShuffleModeEnabled控制播放列表 setPlaybackParameters控制亮度和音量

        // 设置监听
        mPlayer.addListener(mPlayerEventListener)
        mPlayer.addVideoListener(mPlayerVideoListener)
        // 移除监听
        // mPlayer.removeListener(mPlayerEventListener)
        mPlayer.prepare(mediaSource, false, false)
    }

    private fun buildMediaSource(uri: Uri): MediaSource {
        val dataSourceFactory = DefaultDataSourceFactory(context, Util.getUserAgent(context, "you application name"))
        return ProgressiveMediaSource.Factory(dataSourceFactory)
            .createMediaSource(uri)
    }

        private var mPlayerEventListener = object : EventListener {
        override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
            when (playbackState) {
                Player.STATE_IDLE -> {
                    // 出事状态、播放器停止或播放失败时的状态
                }
                Player.STATE_BUFFERING -> {
                    // 开始加载数据,无法立即从当前位置进行播放
                }
                Player.STATE_READY -> {
                    // ready状态,可以直接从当前位置播放
                }
                Player.STATE_ENDED -> {
                    // 播放完成
                }
            }
        }

        override fun onPlayerError(error: ExoPlaybackException?) {
            // error.getSourceException()可以获取更多信息
            when(error?.type) {
                ExoPlaybackException.TYPE_SOURCE -> {
                    // 加载资源时出错
                }

                ExoPlaybackException.TYPE_RENDERER -> {
                    // 渲染时出错
                }

                ExoPlaybackException.TYPE_UNEXPECTED -> {
                    // 意外
                }

                ExoPlaybackException.TYPE_OUT_OF_MEMORY -> {
                    // OOM
                }

                ExoPlaybackException.TYPE_REMOTE -> {
                    // 远程错误
                }
            }
        }
    }

    private var mPlayerVideoListener = object : VideoListener {
        override fun onVideoSizeChanged(
            width: Int,
            height: Int,
            unappliedRotationDegrees: Int,
            pixelWidthHeightRatio: Float
        ) {

        }

        override fun onRenderedFirstFrame() {

        }

        override fun onSurfaceSizeChanged(width: Int, height: Int) {

        }
    }

资源释放

if (mPlayer != null) {
    mPlayer.removeListener(mPlayerEventListener)
    mPlayer.removeVideoListener(mPlayerVideoListener)
    mPlayer.release()
}

下一篇: 2. ExoPlayer MediaSource简介


results matching ""

    No results matching ""